# -*- coding: utf-8 -*-

import sys
import os
import csv
import pickle

from define import *
from objects import *
from csvdata import *
from mathematics import *
from graph import *
from gui import *
from utils import *
from exception import *
from log import *

class App:

    def __init__ (self, cfg):
	self.cfg = cfg
	
	logger = Logger(self.cfg.debug)
	if self.cfg.error_log:
	    logger.initFile(Utils.abspath(self.cfg.error_log))
	
	self._data = CSVData(self.cfg, Utils.abspath(self.cfg.datafile)).parse(self.cfg.delim) #описание ломанных
	self._vars = CSVVars(self.cfg, Utils.abspath(self.cfg.varfile)).parse(self.cfg.delim) #описание переменных
	
	self.session = None
	
	for var in self._data:
	    logger.debug(self._data[var])
	logger.debug(self._vars)
	
	self.checkData()
	logger.debug(self._vars)
	

    def checkData(self):
	logger = Logger(self.cfg.debug)
	
	_list = self._vars['list']
	_set  = self._vars['set']
	
	for var in _set:
	    parent = _set[var]['parent']
	    if parent:
		try:
		    if not parent in _list:
			s = "'%s' has parent '%s' but it doesn`t present" % (var, parent)
			if self.cfg.ignore:
			    parent = ''
			    logger.warning(s)
			else:
			    raise Handled(s)
		    if not parent in _set:
			s = "'%s' has parent '%s' but it doesn`t present" % (var, parent)
			if self.cfg.ignore:
			    del _list[parent]
			    parent = ''
			    logger.warning(s)
			else:
			    raise Handled(s)
		    if not var in self._data:
			s = 'Nothing known about %s' % (var)
			if self.cfg.ignore:
			    parent = _set[var]['parent']
			    if parent and parent in _list:
				try:
				    idx = _list[parent].index(var)
				    del _list[parent][idx]
				except ValueError:
				    pass
			    logger.warning(s)
			else:
			    raise Handled(s)
		except Handled, e:
		    raise Handled("%s" % e)
	
	to_del = []
	for var in _list:
	    if not len(_list[var]):
		to_del.append(var)
	for var in to_del:
	    del _list[var]
		    
	if not len(_list):
	    raise Handled('No vars')

    def dialog (self):
	sessions = {'name': [], 'data': []}
	for s in os.listdir(Utils.abspath(self.cfg.savepath)):
	    sess = Utils.abspath(self.cfg.savepath) + '/' + s
	    if os.path.isdir(sess) and not os.path.islink(sess):
		data = Utils.loadSession(sess + '/' + self.cfg.session_file)
		if data:
		    sessions['name'].append(s)
		    sessions['data'].append(data)
	
	Dialog(sessions, self._vars, self.cfg.width, self.cfg.height)
	
	if not 'state' in self._vars:
	    return False
	
	state = self._vars['state']
	
	if not 'session' in state:
	    return False
	
	if not 'decision' in state:
	    return False
	
	if not 'step' in state['decision']:
	    return False
	
	if state['decision']['step'] == eSteps.Step1:
	    if not 'lp2' in state:
		return False
	    
	    if not 'values' in state:
		return False
	
	if not 'shadow' in state['decision']:
	    return False

	return True
	
    def saveData (self):
	state = self._vars['state']
	logger = Logger(self.cfg.debug)

	logger.debug(state)
	
	if 'name' in state['session'] and 'exists' in state['session']:
	    session = Utils.abspath(self.cfg.savepath) + '/' + state['session']['name']
	    if state['session']['exists'] and os.path.exists(session):
		pass
	    else:
		os.mkdir(session)
	    
	    if state['decision']['step'] == eSteps.Step1:
		Utils.saveSession(session + '/' + self.cfg.session_file, state['decision'])
	    
	    self.session = session
	else:
	    raise Handled('Session data not set')
	    
	
	if state['decision']['step'] == eSteps.Step1:
	    
	    if not state['lp2']:
		raise Handled('Can`t store data for', state['lp2'])
		
	else: # eSteps.Step2:
	    state['lp2'] = 'decision'
		
	dir_path = self.session + '/' + state['lp2']
	if os.path.exists(dir_path):
	    Utils.cleanDir(dir_path)
	else:
	    os.mkdir(dir_path)

	logger.initDataFile(dir_path + '/data.txt')
	if state['decision']['step'] == eSteps.Step1:
	    logger.info(state['lp2'])
		
	if not 'decision' in state['decision']:
	    raise Handled('Data not set', 'decision')
		    
	decision = state['decision']['decision']
	logger.info("decision: " + Decisions[decision])
	
	if not 'group' in state['decision']:
	    raise Handled('Data not set', 'group')
	
	if not 'shadow' in state['decision']:
	    raise Handled('Data not set', 'shadow')
	
	group = state['decision']['group']
	shadow = state['decision']['shadow']
	if decision  == eDecisions.Groups:
	    logger.info("groups: " + Groups[group])
	else:
	    logger.info("shadows: " + Shadows[shadow])
	
	if state['decision']['step'] == eSteps.Step1:
	    
	    for var in state['values']:
		logger.info("%s=%g" % (var, state['values'][var]))
		
	    

    def prepareData (self):
	
	state = self._vars['state']
	logger = Logger(self.cfg.debug)
	x = linspace(0, 1, self.cfg.step)
	
	data = {
	    'names': [],
	    'peaks': [],
	}
	
	if state['decision']['step'] == eSteps.Step1:
	
	    funcs = []
	    for varname in state['values']:
		var = self._data[varname]
		xi  = state['values'][varname]
		if xi < 0:
		    continue
		
		fs = var.f(x)
		fx = var.fx(x, xi)
		xs = [xi]*len(x)
		extrems = Math.extremums(x, fx)
		peak = points2peak(extrems['x'], extrems['y'])
		logger.debug(varname)
		logger.debug(peak)
		
		funcs = []
		for f in fs:
		    funcs.append({'fx': f, 'color':'b'})
		funcs.append({'x': xs, 'fx': x, 'color': 'r--'})
		filename = "%s/%s/%s" % (self.session, state['lp2'], varname)
		self.graphFx(x, fx, filename, funcs=funcs)
		
		data['names'].append(varname)
		data['peaks'].append(peak)
		
	else: # eSteps.Step2
	    for s in os.listdir(self.session):
		datafile = self.session + '/' + s + '/' + self.cfg.data_file
		if os.path.exists(datafile) and os.path.isfile(datafile):
		    try:
			logger.debug(datafile)
			peak = Utils.loadSession(datafile)
			if peak:
			
			    filename = "%s/%s/%s" % (self.session, state['lp2'], s)
			    self.graphFx(x, peak.f(x), filename)
			    
			    data['names'].append(s)
			    data['peaks'].append(peak)
		    except Exception, e:
			pass
	
	logger.debug(data)
	
	return data

    def calc(self, data):
	
	state = self._vars['state']
	logger = Logger(self.cfg.debug)
	x = linspace(0, 1, self.cfg.step) #step set in config
	
	pila = self._data['pila']
	pila.peaks.sort()
	
	decision = []
	decision_mode = state['decision']['decision']
	if decision_mode == eDecisions.Shadows:
	    decision = self.calcShadows(x, data, pila)
	elif decision_mode == eDecisions.Groups:
	    decision = self.calcGroups(x, data, pila)
	elif decision_mode == eDecisions.ShadowsML:
	    decision = self.calcShadows(x, data, pila, True)
	else:
	    raise Handled('Unknown decision', decision_mode)
	
	logger.debug(decision)
	
	funcs = []
	extrems = Math.extremums(x, decision)
	peak = points2peak(extrems['x'], extrems['y'])
	logger.debug(extrems)
	
	if state['decision']['step'] == eSteps.Step1:
	    # save data
	    filename = "%s/%s/%s" % (self.session, state['lp2'], self.cfg.data_file)
	    Utils.saveSession(filename, peak)
	elif state['decision']['step'] == eSteps.Step2:
	    # дефаззификация
	    defazzy = []
	    
	    for m in range(len(Methods)):
		
		value = None
		if m == eMethods.Gravity:
		    value =  peak.gravity()
		
		elif m == eMethods.Mediana:
		    value =  Math.mediana(peak._farea, peak.lines[0].p0.x, peak.lines[-1].p1.x)
		
		elif m == eMethods.MoM:
		    le = len(extrems['y'])
		    cnt = 0
		    value = 0
		    for i in  range(1, le-1):
			if extrems['y'][i-1] < extrems['y'][i] and extrems['y'][i] > extrems['y'][i+1]:
			    value += extrems['x'][i]
			    cnt += 1
		    if cnt:
			value /= cnt
		    else:
			value = None
		elif m == eMethods.SoM:
		    le = len(extrems['y'])
		    min_ = 1
		    for i in  range(1, le-1):
			if extrems['y'][i-1] < extrems['y'][i] and extrems['y'][i] > extrems['y'][i+1]:
			    if extrems['y'][i] < min_:
				min_ = extrems['y'][i]
				value = extrems['x'][i]
		
		elif m == eMethods.LoM:
		    le = len(extrems['y'])
		    max_ = 0
		    for i in  range(1, le-1):
			if extrems['y'][i-1] < extrems['y'][i] and extrems['y'][i] > extrems['y'][i+1]:
			    if extrems['y'][i] > max_:
				max_ = extrems['y'][i]
				value = extrems['x'][i]

		if value != None:
	    	    logger.info('%s: %g' % (Methods[m], value))
		    funcs.append({
			'x': [value]*len(x),
			'fx': x,
			'color': '--',
			'legend': Methods[m]
		    })
		
	# save image
	filename = "%s/%s/%s" % (self.session, state['lp2'], state['lp2'])
	self.graphFx(x, decision, filename, funcs=funcs)
    
    
    def calcShadows(self, x, data, pila, isML=False):
	state = self._vars['state']
	logger = Logger(self.cfg.debug)
	
	if not 'shadow' in state['decision']:
	    raise Handled('Shadow criteria not set')
	    
	shadow_mode = state['decision']['shadow']
	
	# вспомомгательные функции
	max_ = lambda x, y: max(x, y)
	min_ = lambda x, y: min(x, y)
	max1_ = lambda x, y: max(1-x, y)
	
	# определяем близость ЛПх для зубов пилы
	pila_weights = [0] * len(pila.peaks)
	var_count = 0
	for i in range(len(data['peaks'])):
	    name = data['names'][i]
	    var  = data['peaks'][i]
	    
	    if not var.lines:
		logger.warning('%s has no decisions with pila %d' % (name, i+1) )
		continue
	    
	    cog_var = var.gravity()
	    
	    min_distance = 65536
	    nearest = -1
	    for j in range(len(pila.peaks)):
		peak = pila.peaks[j]
		
		cog_peak = peak.gravity()
		distance = abs(cog_peak - cog_var)
		if distance < min_distance:
		    min_distance = distance
		    nearest = j
	    
	    if nearest >= 0:
		pila_weights[nearest] += 1
		var_count += 1
	
	# нормируем, переводим в процент остатка
	for i in range(len(pila_weights)):
	    if var_count:
		tmp = 1 - 0.5 * (1 - pila_weights[i] / (0.0 + var_count) )
		pila_weights[i] = tmp
	
	# формирование теней функции ЛП
	groups = []
	power_pila = []
	for i in range(len(pila.peaks)):
	    peak = pila.peaks[i]
	    
	    group = []
	    for j in range(len(data['peaks'])):
	    
		name = data['names'][j]
		var  = data['peaks'][j]
		
		if not var.lines:
		    logger.warning('%s has no decisions with pila %d' % (name, i+1) )
		    continue
		
		filename = "%s/%s/%s-pila%d" % (self.session, state['lp2'], name, i+1)
		
		z = self.calcVar(x, var, peak, filename, isML)
		group.append(z)
	    groups.append(group)
	    power_pila.append(Math.power(peak.f(x)))
	
	decisions = []
	max_d = 0
	max_delta = -1
	max_power = -1
	# строим графики пересечений и возвращаем множество с максимальной мощностью
	for k in range(len(groups)):
	    # weights
	    weight = pila_weights[k]
	    kx_ = lambda x : weight * x
	    
	    funcs = []
	
	    group = groups[k]
	    l = len(group)
	    # compression
	    d = group[0]
	    for i in range(1, l):
		d = map(min_, d, group[i])
	
	    if isML:
		peak = pila.peaks[k].f(x)
		funcs.append({'fx': peak, 'color': 'm'})
		for i in range(1, l):
		    d =  map(max1_, d, peak)
	
	    for g in group:
		funcs.append({'fx': g, 'color': '--'})
	    
	    funcs.append({'fx': d, 'color': 'b'})
	    
	    decision = map(kx_, d)
	    power = Math.power(decision)
	    power_d = Math.power(d)
	    delta = power / power_pila[k]
	    
	    #defazzy
	    extrems = Math.extremums(x, decision)
	    p = points2peak(extrems['x'], extrems['y'])
	    cog = p.gravity()
	    cog_d = [cog] * len(x)
	    funcs.append({'x': cog_d, 'fx': x, 'color': '--'})
	    LoM = Math.max_(decision)
	    
	    filename = "%s/%s/pila%d" % (self.session, state['lp2'], k + 1)
	    if isML:
		title = "|M|=%g, |M'|=%g, w=%g LoM=%g" % (power, power_d, weight, LoM)
	    else:
		title = "|M|=%g, |M'|=%g, w=%g delta=%g%% LoM=%g" % (power, power_d, weight, 100*delta, LoM)
	    self.graphFx(x, decision, filename, title=title, funcs=funcs)
	    
	    decisions.append(decision)
	    if isML or shadow_mode == eShadows.Max:
		if max_power < power:
		    max_power = power
		    max_delta = delta
		    max_d = k
		elif max_power == power:
		    if max_delta < delta:
			max_power = power
			max_delta = delta
			max_d = k
	    elif shadow_mode == eShadows.MaxPercent:
		if max_delta < delta:
		    max_delta = delta
		    max_d = k
	    elif shadow_mode == eShadows.LoM:
		if max_power < LoM:
		    max_power = LoM
		    max_d = k
	
	if not len(decisions):
	    raise Handled('No decisions')
	
	return decisions[max_d]
	
    def calcVar (self, x, var, pila, filename, isML=False): 
    
	logger = Logger(self.cfg.debug)
	lx = len(x)
	
	# графики
	f = var.f(x)
	f_pila = pila.f(x)
	
	#calculate cetners of gravity: cog
	cog_f = var.gravity()
	cog_pila = pila.gravity()
	cog_distance = abs(cog_f - cog_pila)
	
	#cog lines
	l_cog_f = [cog_f] * lx
	l_cog_pila = [cog_pila] * lx
	
	koef = (cog_f/cog_pila) if cog_pila else 1
	# вспомогательные функции
	mirror = lambda x: x*koef        # функция построения линии отражения
	
	# точки изгиба
	points = var.points()
	# для фигуры тени
	points['z'] = []

	if len(points['x']) < 2:
	    raise Handled("Can`t determinate extemums for var", name)
	    
	# проекции функции ЛП на линию "тени"
	cross = {'x': [], 'y': []}
	for i in range(len(points['x'])):
	    point = points['x'][i]
	    points['z'].append( point / koef )
	    cross['x'].append( [ point ] * lx )
	    cross['y'].append( [ point / koef ] * lx)
    
	# "тень"
	peak_shadow = points2peak(points['z'], points['y'])
	f_shadow = peak_shadow.f(x)
	
	# линия отражения
	l_mirror = mirror(x)
	for i in range(lx):
	    if points['z'][0] > x[i] or points['z'][-1] < x[i]:
		l_mirror[i] = 0
	
	#перескаем тень и зуб пилы
	f_res = [0.0] * lx
	f_res_pre = [0.0] * lx
	for i in range(lx):
	    if isML:
		f_res_pre[i] = f_shadow[i]
	    else:
		f_res_pre[i] = min(f_pila[i], f_shadow[i])
		# не образем все, что выше линии отражение (наклонной)
		#f_res[i] = min(f_pila[i], f_shadow[i], l_mirror[i]) # вариант с отрезанием
	    
	    f_res[i] = f_res_pre[i] / (1 + cog_distance) # "приземляем" результат объединнения в зависимости от удаления от зуюа пилы
	
	#строим графики
	neg = lambda x: -x  # функция "отражения"
	
	funcs = []
	# f(x)
	funcs.append({'x': map(neg, f), 'fx': x, 'color': 'b'})
	funcs.append({'fx': l_cog_f, 'color': 'b--'})
	funcs.append({'x': map(neg, x), 'fx': l_cog_f, 'color': 'b--'})
	#pila
	funcs.append({'fx': f_pila, 'color': 'g'})
	funcs.append({'x': l_cog_pila, 'fx': x, 'color': 'g--'})
	#shadow
	funcs.append({'fx': f_shadow, 'color': 'b'})
	#mirror
	funcs.append({'fx': mirror(x), 'color': 'r--'})
	for i in cross['x']:
	    funcs.append({'fx': i, 'color': 'm--'})
	    funcs.append({'x': map(neg, x), 'fx': i, 'color': 'm--'})
	for i in cross['y']:
	    funcs.append({'x': i, 'fx': x, 'color': 'm--'})
	#result
	if not isML:
	    funcs.append({'fx': f_res_pre, 'color': 'k'})
	
	#fx = {
	#    'f'        : f,
	#    'cog_f'    : l_cog_f,
	#    'pila'     : f_pila,
	#    'cog_pila' : l_cog_pila,
	#    'shadow'   : f_shadow,
	#    'mirror'   : mirror(x),
	#    'xcross'   : cross['x'],
	#    'ycross'   : cross['y'],
	#    'preresult': f_res_pre,
	#    'result'   : f_res
	#}
	
	if filename:
	    #self.graphShadow(x, fx, filename)
	    title = ('|M|=%g cog(F)=%g cog(Pila)=%g' % (Math.power(f_res), cog_f, cog_pila))
	    self.graphFx(x, f_res, filename, funcs=funcs, title=title, wide=True)
	
	return f_res

    
    def calcGroups(self, x, data, pila):
	state = self._vars['state']
	logger = Logger(self.cfg.debug)
	
	# вспомомгательные функции
	min_ = lambda x, y: min(x, y)
	lukasevich = lambda x, y: min(1, 1 - x + y)
	
	if not 'group' in state['decision']:
	    raise Handled('Groups decsion mode not set')
	    
	groups_mode = state['decision']['group']
	if groups_mode == eGroups.Lukasevich:
	    sposob = lukasevich
	else:
	    sposob = min_
	
	peaks_data = []
	for peak in data['peaks']:
	    peaks_data.append(peak.f(x))
	
	# разбиваем переменные на группы
	groups = self.getGroups(peaks_data)
	logger.debug(groups)
	
	# Находим ФП группы
	fps = []
	for k in range(len(groups)):
	    group = groups[k]
	    l = len(group)
	    fp = [1.0]*len(x)
	    for i in range(l):
		fp = map(min_, fp, group[i])
	        
	    funcs = []
	    for i in range(l):
		funcs.append({'fx': group[i], 'color': '--'})
	    filename = "%s/%s/group%d" % (self.session, state['lp2'], k + 1)
	    self.graphFx(x, fp, filename, funcs=funcs)
	    fps.append(fp)
	
	# пересечение полученных ФП группы с "пилой" и возвращаем множество с ммаксималной мощностью
	decisions = []
	d_cnt = 0
	max_d = -1
	max_power = 0
	for i in range(len(fps)):
	    fp = fps[i]
	    for j in range(len(pila.peaks)):
		peak = pila.peaks[j]
		f_pila = peak.f(x)
		z = map(sposob, fp, f_pila)
		
		power = Math.power(z)
		filename = "%s/%s/group%d-pila%d" % (self.session, state['lp2'], i+1, j+1)
		self.graphFx(x, z, filename, title=('|M|=%g' % power), funcs=[{'fx': fp,'color':'g', 'legend':"group%d" % (i+1)}, {'fx':f_pila, 'color':'r', 'legend':"pila%d" % (j+1)}])
		decisions.append(z)
		if max_power < power:
		    max_power = power
		    max_d = d_cnt
		elif power > 0 and max_power == power: # если мощности совпали, то выбираем множество, у которого ордината центра тяжести больше
		    e1 = Math.extremums(x, decisions[max_d])
		    p1 = points2peak(e1['x'], e1['y'])
		    g1 = p1.gravity()
		    e2 = Math.extremums(x, decisions[d_cnt])
		    p2 = points2peak(e2['x'], e2['y'])
		    g2 = p2.gravity()
		    if g2 > g1:
			max_power = power
			max_d = d_cnt
		d_cnt += 1
	
	if not len(decisions):
	    raise Handled('No decisions')
	
	return decisions[max_d]

    def getGroups(self, peaks):
	
	logger = Logger(self.cfg.debug)
	groups = []
	groups_idx = []
	
	# вспомогательные функции
	min_ = lambda x, y: min(x, y)
	
	l = len(peaks)
	for k in range(l):
	    group = [peaks[k], ]
	    y = list(peaks[k])
	    idx = [k,]
	    for m in range(l):
		if k == m:
		    continue
		yy = map(min_, y, peaks[m])
		# проверяем пересечения на пустоту
		for yi in yy:
		    if yi > 0:
			y = list(yy)
			group.append(peaks[m])
			idx.append(m)
			break
	    idx.sort()
	    if not idx in groups_idx:
		groups_idx.append(idx)
		groups.append(group)
	
	logger.debug(groups_idx)
	return groups
    
    def graphFx(self, x, fx, filename, **kwargs):

	# обработка доп.параметров
	funcs = Utils.getVar(kwargs, 'funcs')
	title = Utils.getVar(kwargs, 'title')
	wide = Utils.getVar(kwargs, 'wide')
	showLegend = True
	if not title:
	    showLegend = False
	    title = 'f(x)'
	
	graph = Graph()
	if funcs:
	    for f in funcs:
		
		if not 'fx' in f:
		    continue
		
		xx = x
		if 'x' in f:
		    xx = f['x']
		    del f['x']
		
		linetype = None
		
		if 'color' in f:
		    linetype = f['color']
		
		legend = None
		#if 'legend' in f:
		#    legend = f['legend']
		
		graph.add(xx, f['fx'], linetype, legend)
	
	
	graph.add(x, fx, 'k', title, 3)
	graph.save(filename, wide, showLegend)


    def graphShadow(self, x, fx, filename): #unused
	# вспомогательные функции
	neg = lambda x: -x               # функция "отражения"
	
	graph = Graph()
	# f(x)
	graph.add(graph.flip(fx['f']), x, 'b', 'f(x)')
	graph.add(x, fx['cog_f'], 'b--')
	graph.add(map(neg, x), fx['cog_f'], 'b--')
	# pila
	graph.add(x, fx['pila'], 'g', 'pila')
	graph.add(fx['cog_pila'], x, 'g--')
	# shadow
	graph.add(x, fx['shadow'], 'b', 'shadow')
	# mirror
	graph.add(x, fx['mirror'], 'r--', 'mirror')
	for i in fx['xcross']:
	    graph.add(x, i, 'm--')
	    graph.add(map(neg,x), i, 'm--')
	for i in fx['ycross']:
	    graph.add(i, x, 'm--')
	# result
	if (fx['preresult']):
	    graph.add(x, fx['preresult'], 'k')
	graph.add(x, fx['result'], 'k', 'result', 3)
	
	graph.save(filename, True)

